共计 3630 个字符,预计需要花费 10 分钟才能阅读完成。
引入
先来举个例子 :
当你摁下电脑开机键, 你不需要考虑主板是怎么通电的、磁盘是怎么转动的、系统的信息是怎么加载的、里面的一系列化学或者物理变化是怎么样的,你面对的就是一个开关键, 摁下它, 电脑就开起来了.
又比如:
一个玩具制造厂, 制作一个机器人, 我们需要去考虑机器人的每一个细节: 手臂、腿、头、躯干等等,制作简单的玩具不要紧, 如果设计的玩具非常的复杂, 并且这工厂还有很多其他类型的玩具生产(小熊, 佩奇, 芭比公主, 飞机模型等等)
上面我们使用面向过程的思想去设计, 在程序中就会让我们的代码很长, 很杂乱
于是工厂听取了小王的建议, 引进了一台制造机器人玩具的机器, 我们只需要摁下机器的开关, 它就会自动制造机器人
这个时候我们不再去关注是先制造手还是脚还是躯干这些细节, 我们面对的就只是一台机器, 这机器就是一个对象
原理 : 以前我们需要关注制作玩具的每个步骤, 现在我们将步骤都封装到一个机器里面, 留给我们的只剩一个开关, 我们只需要摁开关就可以生产玩具了, 这就是 面向对象的三大特性之一 : 封装, 下面我们将详细展开介绍
一. 封装
1. 什么封装
- 封装是 Python 中面向对象的三大特性之一
- 封装就是隐藏对象的属性和实现细节, 仅对外提供公共访问方式
2. 为什么使用封装
- 提高安全性 : 避免用户对类中属性或方法进行不合理的操作
- 隔离复杂度 : 就以上面的例子为例, 你只需要机器提供给你的开关, 内部结构不需要知道
- 保证内部数据结构完整性 : 很好的避免了外部对内部数据的影响,提高了程序的可维护性
- 提供代码的复用性
3. 封装的原则
- 将不需要对外提供的功能都给隐藏起来
- 把属性都隐藏, 提供公共方法对其方法
二. 隐藏
在 python 中用双下划线开头的方式将属性隐藏起来(设置成私有的)
- 其实这仅仅这是一种 变形操作 且仅仅只在类 定义阶段发生变形
- 类中所有双下划线开头的名称如
__xxx
都会在类定义时自动变形成:_[类名]__xxx
的形式
class Eat:
__breed = " 五花肉 " # 变形 : "_Eat__breed"
def __init__(self):
self.__meat = " 大块 " # 变形 : "_Eat__meat"
def __eat(self): # 变形 : "_Eat__eat"
print(f" 吃了 {self.__meat} 的{self.__breed}")
def eat_meat(self):
self.__eat() # 只有在类的内部才可以通过 "__eat" 的形式访问到
P1 = Eat()
🍔调用类提供的接口(方法)
P1.eat_meat() # 吃了大块的五花肉
🍔调用隐藏的属性 / 方法, 报错
print(P1.breed) # 报错 "AttributeError" 没有该属性
print(P1.__breed) # 报错 "AttributeError" 没有该属性
P1.__eat() # 报错 "AttributeError" 没有该属性
🍔调用变形后的属性 / 方法(不推荐这么做)
print(P1._Eat__breed) # 五花肉
print(P1._Eat__meat) # 大块
P1._Eat__eat() # 吃了大块的五花肉
隐藏属性的查找示例
class Bar:
def __f1(self): # 变形 "_Bar__f1"
print("i am Bar_f1")
def f2(self):
print("i am Bar_f2")
self.__f1() # 等同于 "self._Bar__f1"
class Foo(Bar):
def __f1(self): # 变形 "_Foo__f1"
print("i am Foo_f1")
Foo().f2()
''' 输出
i am Bar_f2
i am Bar_f1
'''
重要步骤 : 当找到
self.__f1()
的时候, 其实是找到self._Bar__f1
, 于是先去自己 (Foo) 里面去找, 结果找不到自己里面的, 然后到父类中去找, 最终打印 "i am Bar_f1"
三. 封装的两个层面
1. 第一层面(公有 : public)
- public : 公有属性的类变量和类函数,在类的外部、类内部以及子类中,都可以正常访问
🍔我们来制作一个机器人, 分别要考虑制作不同部件, 每个方法都得调用一下
class Rebot:
def Head(self):
print(" 制作头 ")
def Hand(self):
print(" 制作手 ")
def Foot(self):
print(" 制作脚 ")
def Body(self):
print(" 制作躯干 ")
def Fit(self):
print(" 机器人合体 ")
P1 = Rebot()
🍔没有进行封装, 我们需要进行每一个部件的调用
P1.Hand() # 制作手
P1.Head() # 制作头
P1.Foot() # 制作脚
P1.Body() # 制作躯干
P1.Fit() # 机器人合体
🍔当我们进行封装, 提供给用户一个 "Auto" 接口(方法)
def Auto(self): # 提示: 这个 Auto 方法是在类里面的, 方便讲解我才放在这个位置
self.Hand()
self.Head()
self.Foot()
self.Body()
self.Fit()
🍔用户只需要调用 "Auto" 这个方法就可以一步完成上面的所有步骤
P1.Auto()
''' 输出
制作手
制作头
制作脚
制作躯干
机器人合体
'''
如果这样的话, 使用者还是可以调用里面机器人的制作细节, 如果我们不想让使用者使用到那些方法, 我们就可以将细节部分给隐藏起来, 只提供一个 "Auto" 方法,👇👇
2. 第二层面 (私有 : private)
- private:私有属性的类变量和类函数,只有在类的内部使用,类的外部以及子类都无法使用
- 如果类中的变量和函数,其名称以 双下划线
__
开头,则该变量或函数为私有的 - 如果以 单下划线
_
开头 的属性和方法, 我们约定俗成的视其为私有, 就是上面隐藏里说到的变形后的结果, 虽然能正常调用, 但不建议这么做
class Rebot:
def __Head(self): # 变形 : "_Rebot__Head"
print(" 制作头 ")
def __Hand(self): # 变形 : "_Rebot__Hand"
print(" 制作手 ")
def __Foot(self): # 变形 : "_Rebot__Foot"
print(" 制作脚 ")
def __Body(self): # 变形 : "_Rebot__Body"
print(" 制作躯干 ")
def __Fit(self): # 变形 : "_Rebot__Fit"
print(" 机器人合体 ")
def Auto(self): # 提供给使用者的接口(方法)
self.__Hand()
self.__Head()
self.__Foot()
self.__Body()
self.__Fit()
P1 = Rebot()
P1.Auto()
''' 输出
制作手
制作头
制作脚
制作躯干
机器人合体
'''
# P1.__Hand() # 报错 : "AttributeError" 没有该属性
# P1.__Head() # 报错 : "AttributeError" 没有该属性
🍔如果特别想调用, 可以这么调用, 但非常不建议
P1._Rebot__Hand() # 制作头
P1._Rebot__Head() # 制作手
如此就实现了给使用者只看到能让他用的东西
不给用户直接使用的东西隐藏起来
四. 小示例
- 定义一个人类, 然后实例出一个对象, 该对象的名字不能以 "sb" 开头
- 把 "name" 这个属性隐藏起来, 外部访问不到
- "name" 隐藏起来后, 对象自己该如何获取这个属性? 提供一个打印名字的方法
- 定义一个修改名字的方法
class Person:
def __init__(self,name):
if not name.startswith("sb"):
self.__name = name
else:
print(" 名字不能以 'sb' 开头 ")
def print_name(self): # 打印名字的方法
print(self.__name)
def change_name(self,name): # 修改名字的方法
if not name.startswith("sb"):
self.__name = name
print(" 修改成功 ")
else:
print(" 名字不能以 'sb' 开头 ")
P1 = Person("sb_hahah") # 名字不能以 'sb' 开头
P2 = Person("shawn")
# print(P2.__name) # 报错 : "AttributeError" 没有该属性
# print(P2.name) # 报错 : "AttributeError" 没有该属性
P2.print_name() # shawn
P2.change_name("sb_kkkk") # 名字不能以 'sb' 开头
P2.change_name("xing") # 修改成功
P2.print_name() # xing
如此一来保证了数据的安全
补充 :
- 模块中也可以使用隐藏属性, 在属性前加 "_", 例 : "_name" (单双下划线都可以)
- 希望只在模块内部使用, 而外部无法使用
- 针对的是
from xxx import *
方法中的*
星号 - 如果想使用, 可以以
from xxx impoer _name
这种方式进行导入